#include <stdio.h>
#include <assert.h>

#include "fontmgr_impl.h"

namespace agg
{

	//------------------------------------------------------font_cache_manager
	template<class FontEngine> class font_cache_manager
	{
	public:
		typedef FontEngine font_engine_type;
		typedef font_cache_manager<FontEngine> self_type;
		typedef typename font_engine_type::path_adaptor_type   path_adaptor_type;

	public:
		font_cache_manager() {}
		~font_cache_manager() {}
		
	public:
        void init_embedded_adaptors(const glyph_cache* gl, 
			double x, double y, double scale = 1.0)
        {
        	if(gl && gl->data_type == glyph_data_outline)
        		m_path_adaptor.init(gl->data, gl->data_size, x, y, scale);
        }

        path_adaptor_type&   path_adaptor()   { return m_path_adaptor; }

    private:
    	font_cache_manager(const self_type&);
    	const self_type& operator = (const self_type&);

 private:
        path_adaptor_type   m_path_adaptor;
    };

};	// end of namespace agg

namespace visgrph
{

using namespace grphfntcache;

// static data
static grphoal::syncobject s_fntmgrsync;

int fontinfo_impl::avl_compare(avl_node_t* a, avl_node_t* b)
{
	fontinfo_impl* da = AVLNODE_ENTRY(fontinfo_impl, avlnode, a);
	fontinfo_impl* db = AVLNODE_ENTRY(fontinfo_impl, avlnode, b);
	int ret = strcmp(da->m_fontname, db->m_fontname);

	if (ret > 0) return 1;
	else if (ret < 0) return -1;
	else return (da->m_faceid > db->m_faceid) ? 1
		: ((da->m_faceid < db->m_faceid) ? -1 : 0);
}

void fontinfo_impl::genfid(const char* fntname, const char* filename,
		unsigned int faceid, grphoal::uint128_t& fid)
{
	char buffer[256];
	char *p = buffer;
	size_t fntnsz = strlen(fntname);
	size_t filnsz = strlen(filename);

	assert(fntnsz + filnsz < 230);
	memcpy(p, fntname, fntnsz); p += fntnsz;
	memcpy(p, filename, filnsz); p += fntnsz;
	*((size_t*)p) = faceid; p += sizeof(size_t);
	*((size_t*)p) = (size_t)m_ftype;

	grphoal::md5gen md5(buffer, fntnsz + filnsz + sizeof(size_t) * 2);
	fid = md5.result();
}

void fontinfo_impl::init(const char* fntname, const char* filename,
		unsigned int faceid)
{
	assert(NULL != fntname);

	m_fontname = new char [strlen(fntname) + 1];
	assert(NULL != m_fontname);
	strcpy(m_fontname, fntname);

	m_filename = NULL;
	if (filename) {
		m_filename = new char [strlen(filename) + 1];
		assert(NULL != m_filename);
		strcpy(m_filename, filename);
	}

	m_faceid = faceid;	
	m_global_font_desc = NULL;
}

bool fontinfo_impl::set_font_filename(const char* filename)
{
	if (NULL == filename || NULL != m_filename)
		return false;

	// need sync
	grphoal::autosync async(s_fntmgrsync);

	m_filename = new char [strlen(filename) + 1];
	strcpy(m_filename, filename);
	return true;
}

font_descriptor* fontinfo_impl::get_fontdesc(void)
{
	if (m_global_font_desc)
		return m_global_font_desc;

	if (NULL == m_fontname || NULL == m_filename)
		return NULL;

	// need sync
	grphoal::autosync async(s_fntmgrsync);
	if (m_global_font_desc)
		return m_global_font_desc;

	grphoal::uint128_t fid;
	genfid(m_fontname, m_filename, m_faceid, fid);
	m_global_font_desc = fntcache()->load_font(fid);
	return m_global_font_desc;
}

fontinfo_impl::fontinfo_impl()
: m_fontname(NULL)
, m_filename(NULL)
, m_faceid(0)
, m_fctype(fcs_western)
, m_ftype(fts_unknown)
, m_global_font_desc(NULL)
, m_face(NULL)
, m_fi_b(NULL)
, m_fi_i(NULL)
, m_fi_bi(NULL)
{
}

fontinfo_impl::fontinfo_impl(const char* fntname, const char* filename,
		unsigned int faceid, font_charset fctype, fontstyle ftype)
: m_fctype(fctype)
, m_ftype(ftype)
, m_face(NULL)
, m_fi_b(NULL)
, m_fi_i(NULL)
, m_fi_bi(NULL)
{
	assert(fts_unknown != ftype);
	init(fntname, filename, faceid);
}

fontinfo_impl::~fontinfo_impl()
{
	if (m_fontname)
		delete [] m_fontname;
	if (m_filename)
		delete [] m_filename;
	m_fontname = m_filename = NULL;

	if (m_fi_b) delete m_fi_b;
	if (m_fi_i) delete m_fi_i;
	if (m_fi_bi) delete m_fi_bi;
	m_fi_b = m_fi_i = m_fi_bi = NULL;
}

fontmgr_impl* fontmgr_impl::inst(void)
{
	static fontmgr_impl fntmgr;
	return &fntmgr;
}

fontmgr_impl::fontmgr_impl()
: m_fontinfo_tree(NULL)
, m_prev_glyph_index(0)
, m_last_glyph_index(0)
, m_active_charset(fcs_unknown)
{
	m_feng.flip_y(true);
	for (int i = 0; i < fcs_count; ++i)
	{
		m_default_fonts[i] = NULL;
		m_faces[i] = NULL;
		m_fcache[i] = NULL;
	}
}

fontmgr_impl::~fontmgr_impl()
{	
}

void fontmgr_impl::lock(void)
{
	s_fntmgrsync.sync_start();
}

void fontmgr_impl::unlock(void)
{
	s_fntmgrsync.sync_end();
}

fontinfo_impl* fontmgr_impl::add_font(const char* fntname,
	const char* filename, unsigned int faceid,
	font_charset fcs, fontstyle ftype)
{
	if (NULL == filename) return NULL;
	if (fntname && fts_unknown == ftype)
		return NULL;

	char buf[sizeof(fontinfo_impl) > 64 ? sizeof(fontinfo_impl) : 64];

	// need sync
	grphoal::autosync async(s_fntmgrsync);
	if (NULL == fntname)
	{
		if (!m_feng.get_font_info(filename, (int)faceid, ftype, buf))
			return false;
		fntname = (char*)buf;
	}

	// see if the font exists
	fontinfo_impl* dummyfi = reinterpret_cast<fontinfo_impl*>(buf);
	dummyfi->m_fontname = const_cast<char*>(fntname);
	dummyfi->m_faceid = faceid;

	fontinfo_impl* fp;
	avl_node_t *ret = avl_find(m_fontinfo_tree, &(dummyfi->avlnode), fontinfo_impl::avl_compare);
	if (ret)
	{
		fp = AVLNODE_ENTRY(fontinfo_impl, avlnode, ret);
		if (fts_regular == ftype)
			fp->set_font_filename(filename);
	}
	else
	{
		fp = new fontinfo_impl(fntname,
			(fts_regular == ftype) ? filename : NULL,
			faceid, fcs, ftype); assert(NULL != fp);
		avl_insert(&m_fontinfo_tree, &(fp->avlnode), fontinfo_impl::avl_compare);
	}

	switch (ftype)
	{
		case fts_bold:
			if (!fp->m_fi_b) {
				fp->m_fi_b = new fontinfo_impl(fntname, filename,
					faceid, fcs, fts_bold);
				assert(NULL != fp->m_fi_b);
			} return fp->m_fi_b;

		case fts_italic:
			if (!fp->m_fi_i) {
				fp->m_fi_i = new fontinfo_impl(fntname, filename,
					faceid, fcs, fts_italic);
				assert(NULL != fp->m_fi_i);
			} return fp->m_fi_i;

		case fts_bold_italic:
			if (!fp->m_fi_bi) {
				fp->m_fi_bi = new fontinfo_impl(fntname, filename,
					faceid, fcs, fts_bold_italic);
				assert(NULL != fp->m_fi_bi);
			} return fp->m_fi_bi;

		default: return fp;
	}
	return NULL;
}

fontinfo_impl* fontmgr_impl::find_font(const char* fntname,
	unsigned int faceid, fontstyle ftype)
{
	if (NULL == fntname || fts_unknown == ftype)
		return NULL;

	// need sync
	grphoal::autosync async(s_fntmgrsync);

	// see if the font exists
	char buf[sizeof(fontinfo_impl)];
	fontinfo_impl* dummyfi = reinterpret_cast<fontinfo_impl*>(buf);
	dummyfi->m_fontname = const_cast<char*>(fntname);
	dummyfi->m_faceid = faceid;

	fontinfo_impl* fp = NULL;
	avl_node_t *ret = avl_find(m_fontinfo_tree, &(dummyfi->avlnode), fontinfo_impl::avl_compare);
	if (ret) fp = AVLNODE_ENTRY(fontinfo_impl, avlnode, ret);
	else return NULL;

	switch (ftype)
	{
		case fts_regular:
			return fp;
		case fts_bold:
			return fp->m_fi_b;
		case fts_italic:
			return fp->m_fi_i;
		case fts_bold_italic:
			return fp->m_fi_bi;
		default: return NULL;
	}
}

bool fontmgr_impl::set_default(font_charset dft, fontinfo_impl* fnt)
{
	if (dft < 0 || dft >= fcs_max || NULL == fnt)
		return false;

	// need sync
	grphoal::autosync async(s_fntmgrsync);

	m_default_fonts[dft] = fnt;
	font_charset fcs = (dft == fcs_western) ? fcs_eastern : fcs_western;
	if (!m_default_fonts[fcs]) m_default_fonts[fcs] = fnt;
	return true;
}

fontinfo_impl* fontmgr_impl::get_default(font_charset dft)
{
	if (dft < 0 || dft >= fcs_max)
		return NULL;

	return m_default_fonts[dft];
}

fontinfo_impl** fontmgr_impl::_get_default(font_charset dft)
{
	if (dft < 0 || dft >= fcs_max)
		return NULL;

	return &m_default_fonts[dft];
}

font_charset fontmgr_impl::check_font_default(fontinfo_impl* fnt)
{
	if (NULL == fnt)
		return fcs_unknown;

	// need sync
	grphoal::autosync async(s_fntmgrsync);
	return check_font_default_unlock(fnt);
}

font_charset fontmgr_impl::check_font_default_unlock(fontinfo_impl* fnt)
{
	for (int i = 0; i < fcs_count; ++i)
	{
		if (m_default_fonts[i] == fnt)
			return (font_charset)(i + 1);
	}
	return fcs_unknown;
}

// this function is not locked
bool fontmgr_impl::textout_reset(fcache_adaptor& fca)
{
	m_active_charset = fcs_western;
	if (!m_feng.load_font(m_faces[fcs_western], agg::glyph_ren_outline))
		return false;

	if (!load_font_size(fca.fontsize()))
		return false;

	m_prev_glyph_index = m_last_glyph_index = 0;

	for (int i = fcs_western; i < fcs_max; ++i)
	{
		// todo: decref if necessary
		m_fcache[i] = NULL;
	}
	return true;
}

bool fontmgr_impl::activate_fcache_adaptor(fcache_adaptor& fca)
{
	fontinfo_impl* fi_w = fca.get_fontinfo(fcs_western);
	fontinfo_impl* fi_e = fca.get_fontinfo(fcs_eastern);

	// need sync
	grphoal::autosync async(s_fntmgrsync);

	m_faces[fcs_western] = m_faces[fcs_eastern] = NULL;
	void* face = fi_w->face();
	if (!face)
	{
		face = m_feng.load_font(fi_w->filename(), fi_w->faceid(), agg::glyph_ren_outline);
		if (!face) return false;
		fi_w->face(face);
	}
	m_faces[fcs_western] = face;

	face = fi_e->face();
	if (!face)
	{
		face = m_feng.load_font(fi_e->filename(), fi_e->faceid(), agg::glyph_ren_outline);
		if (!face) return false;
		fi_e->face(face);
	}
	m_faces[fcs_eastern] = face;
	return textout_reset(fca);
}

agg::glyph_cache* fontmgr_impl::glyph(fcache_adaptor& fa, unsigned int glyph_code)
{
	// need sync
	grphoal::autosync async(s_fntmgrsync);

	font_charset original = m_active_charset;

	// first we find glyph from western font
	active_fcache(fa, fcs_western);
	agg::glyph_cache* cache = glyph(glyph_code);
	if (cache) goto success;

	// then we try to use eastern font
	active_fcache(fa, fcs_eastern);
	cache = glyph(glyph_code);
	if (cache) goto success;

	// restore to original
	active_fcache(fa, original);
	return NULL;

success:
	// adjust for kerning
	if (m_active_charset != original)
		m_prev_glyph_index = m_last_glyph_index = 0;
	return cache;
}

// set the font size
// this function is not locked
bool fontmgr_impl::load_font_size(unsigned int fontsize)
{
	if (!m_feng.width((fontsize >> 16) * f_scale_x * f_subpixel_scale))
		return false;
	return m_feng.height(fontsize & 0xFFFF);
}

// this function is not locked
bool fontmgr_impl::active_fcache(fcache_adaptor& fa, font_charset fc)
{
	bool changed = false;
	if (m_active_charset != fc)
	{
		changed = true;
		m_active_charset = fc;
	}

	// finding glyph needs fcache
	// see if the current fcache available
	if (NULL == m_fcache[fc])
	{
		if (NULL == (m_fcache[fc] = fa.get_fcache(fc)))
			return false;
	}

	if (!changed) return true;

	// if a font changed, re-load font to font engine
	if (!m_feng.load_font(m_faces[fc], agg::glyph_ren_outline))
		return false;
	if (!load_font_size(fa.fontsize()))
		return false;

	return true;
}

agg::glyph_cache* fontmgr_impl::glyph(unsigned int glyph_code)
{
	fcache* fc = m_fcache[m_active_charset];
	if (NULL == fc) return NULL;

	agg::glyph_cache* cache = fntcache()->find_glyph(fc, glyph_code);
	if (cache) return cache;

	unsigned int glyph_index = m_feng.prepare_glyph(glyph_code);
	if (!glyph_index) return NULL;

	m_prev_glyph_index = m_last_glyph_index;
	m_last_glyph_index = glyph_index;

	cache = fntcache()->cache_glyph(fc, glyph_code,
		m_feng.data_size(), m_feng.data_type(),
		m_feng.advance_x(), m_feng.advance_y());
	if (NULL == cache) return NULL;
	m_feng.write_glyph_to((agg::int8u*)cache->data);
	return cache;
}

///--------------- fcache_adaptor -------------------------------------------------------------
fcache_adaptor::fcache_adaptor()
: m_fontsize((16) << 16 | 16)
{
	for (int i = 0; i < fcs_max; ++i)
		m_finfo[i] = NULL;

	m_finfo_ptr[fcs_western] = fontmgr_impl::inst()->_get_default(fcs_western);
	m_finfo_ptr[fcs_eastern] = fontmgr_impl::inst()->_get_default(fcs_eastern);
}

fcache_adaptor::~fcache_adaptor()
{
}

bool fcache_adaptor::init(font_impl* impl)
{
	if (NULL == impl)
		return false;

	if (impl->finfo[fcs_western] && impl->finfo[fcs_western] != m_finfo[fcs_western])
	{
		m_finfo[fcs_western] = impl->finfo[fcs_western];
		m_finfo_ptr[fcs_western] = &m_finfo[fcs_western];
	}
	if (impl->finfo[fcs_eastern] && impl->finfo[fcs_eastern] != m_finfo[fcs_eastern])
	{
		m_finfo[fcs_eastern] = impl->finfo[fcs_eastern];
		m_finfo_ptr[fcs_eastern] = &m_finfo[fcs_eastern];
	}
	m_fontsize = impl->fontsize;
	return true;
}

bool fcache_adaptor::init(fontinfo_impl* fi, unsigned int fontsize)
{
	font_charset fcs = fi->get_charset();
	if (fi != m_finfo[fcs])
	{
		m_finfo[fcs] = fi;
		m_finfo_ptr[fcs] = &m_finfo[fcs];
	}
	m_fontsize = fontsize;
	return true;
}

fcache* fcache_adaptor::get_fcache(font_charset fc)
{
	if (fc < 0 || fc >= fcs_max)
		return NULL;

	// need sync
	grphoal::autosync async(s_fntmgrsync);
	return load_fcache(fc);
}

// this function is not locked
fcache* fcache_adaptor::load_fcache(font_charset fc)
{
	fcache* ret;
	fontinfo_impl* fi = get_fontinfo(fc);
	if (!fi) return NULL;

	font_descriptor* fdesc = fi->get_fontdesc();
	if (NULL == fdesc) return NULL;

	ret = fntcache()->load_font_cache(fdesc, m_fontsize);
	if (NULL == ret) return NULL;

	// todo: addref if necessary
	return ret;
}

}; // namespace visgrph
/* EOF */
